
import { Command } from './command.js';
import { Editor } from './editor.js';

class History {
  editor:Editor;
  undos:Command[]=[];
  redos:Command[]=[];
  lastCmdTime:number=Date.now();
  idCounter:number=0;
  historyDisabled:boolean = false;

	constructor( editor:Editor ) {

		this.editor = editor;

		// signals

		const scope = this;

		this.editor.signals.startPlayer.add( function () {

			scope.historyDisabled = true;

		} );

		this.editor.signals.stopPlayer.add( function () {

			scope.historyDisabled = false;

		} );

	}

	execute( cmd:Command, optionalName?:string ) {

		const lastCmd = this.undos[ this.undos.length - 1 ];
		const timeDifference = Date.now() - this.lastCmdTime;

		const isUpdatableCmd = lastCmd &&
			lastCmd.updatable &&
			cmd.updatable;

		if ( isUpdatableCmd && cmd.type === 'SetScriptValueCommand' ) {

			// When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored

			lastCmd.update( cmd );
			cmd = lastCmd;

		} else if ( isUpdatableCmd && timeDifference < 500 ) {

			lastCmd.update( cmd );
			cmd = lastCmd;

		} else {

			// the command is not updatable and is added as a new part of the history

			this.undos.push( cmd );
			cmd.id = ++ this.idCounter;

		}

		cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
		cmd.execute();
		cmd.inMemory = true;

		

		this.lastCmdTime = Date.now();

		// clearing all the redo-commands

		this.redos = [];
		this.editor.signals.historyChanged.dispatch( cmd );

	}

	undo() {

		if ( this.historyDisabled ) {

			console.warn( 'Undo/Redo disabled while scene is playing.' );
			return;

		}

		let cmd = undefined;

		if ( this.undos.length > 0 ) {

			cmd = this.undos.pop();

			

		}

		if ( cmd !== undefined ) {

			cmd.undo();
			this.redos.push( cmd );
			this.editor.signals.historyChanged.dispatch( cmd );

		}

		return cmd;

	}

	redo() {

		if ( this.historyDisabled ) {

			console.warn( 'Undo/Redo disabled while scene is playing.' );
			return;

		}

		let cmd = undefined;

		if ( this.redos.length > 0 ) {

			cmd = this.redos.pop();

		

		}

		if ( cmd !== undefined ) {

			cmd.execute();
			this.undos.push( cmd );
			this.editor.signals.historyChanged.dispatch( cmd );

		}

		return cmd;

	}

	

	clear() {

		this.undos = [];
		this.redos = [];
		this.idCounter = 0;

		this.editor.signals.historyChanged.dispatch();

	}

	goToState( id:number ) {

		if ( this.historyDisabled ) {

			console.warn( 'Undo/Redo disabled while scene is playing.' );
			return;

		}

		this.editor.signals.sceneGraphChanged.active = false;
		this.editor.signals.historyChanged.active = false;

		let cmd = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined;	// next cmd to pop

		if ( cmd === undefined || id > cmd.id ) {

			cmd = this.redo();
			while ( cmd !== undefined && id > cmd.id ) {

				cmd = this.redo();

			}

		} else {

			while ( true ) {

				cmd = this.undos[ this.undos.length - 1 ];	// next cmd to pop

				if ( cmd === undefined || id === cmd.id ) break;

				this.undo();

			}

		}

		this.editor.signals.sceneGraphChanged.active = true;
		this.editor.signals.historyChanged.active = true;

		this.editor.signals.sceneGraphChanged.dispatch();
		this.editor.signals.historyChanged.dispatch( cmd );

	}

	enableSerialization( id:number ) {

		/**
		 * because there might be commands in this.undos and this.redos
		 * which have not been serialized with .toJSON() we go back
		 * to the oldest command and redo one command after the other
		 * while also calling .toJSON() on them.
		 */

		this.goToState( - 1 );

		this.editor.signals.sceneGraphChanged.active = false;
		this.editor.signals.historyChanged.active = false;

		let cmd = this.redo();
		while ( cmd !== undefined ) {

			

			cmd = this.redo();

		}

		this.editor.signals.sceneGraphChanged.active = true;
		this.editor.signals.historyChanged.active = true;

		this.goToState( id );

	}

}

export { History };